package com.shop.cereshop.commons.utils;

/**
 * GeoHash 工具类
 * <p>
 * 根据传入的经纬度生成指定长度的 geohash 字符串。
 * 支持常见长度（1-12），超出范围会被截断或抛出异常。
 */
public class GeoHashUtil {

    /**
     * geohash 编码使用的 32 进制字符表
     */
    private static final char[] BASE32 = {
            '0','1','2','3','4','5','6','7','8','9',
            'b','c','d','e','f','g','h','j','k','m',
            'n','p','q','r','s','t','u','v','w','x','y','z'
    };

    private static final double[] LAT_RANGE = {-90.0d, 90.0d};
    private static final double[] LON_RANGE = {-180.0d, 180.0d};

    private GeoHashUtil() {
    }

    /**
     * 生成 geohash 编码
     *
     * @param latitude  纬度，范围 [-90, 90]
     * @param longitude 经度，范围 [-180, 180]
     * @param length    期望 geohash 长度（常用 1-12）
     * @return geohash 字符串
     * @throws IllegalArgumentException 参数越界或长度非法
     */
    public static String encode(double latitude, double longitude, int length) {
        if (Double.isNaN(latitude) || Double.isNaN(longitude)) {
            throw new IllegalArgumentException("latitude or longitude is NaN");
        }
        if (latitude < LAT_RANGE[0] || latitude > LAT_RANGE[1]) {
            throw new IllegalArgumentException("latitude out of range [-90,90]: " + latitude);
        }
        if (longitude < LON_RANGE[0] || longitude > LON_RANGE[1]) {
            throw new IllegalArgumentException("longitude out of range [-180,180]: " + longitude);
        }
        if (length <= 0) {
            throw new IllegalArgumentException("length must be positive");
        }

        StringBuilder geohash = new StringBuilder(length);

        double[] latInterval = {LAT_RANGE[0], LAT_RANGE[1]};
        double[] lonInterval = {LON_RANGE[0], LON_RANGE[1]};

        boolean isEvenBit = true;
        int bit = 0;
        int base32CharIndex = 0;

        while (geohash.length() < length) {
            if (isEvenBit) {
                double mid = (lonInterval[0] + lonInterval[1]) / 2.0d;
                if (longitude >= mid) {
                    base32CharIndex = (base32CharIndex << 1) + 1;
                    lonInterval[0] = mid;
                } else {
                    base32CharIndex = (base32CharIndex << 1);
                    lonInterval[1] = mid;
                }
            } else {
                double mid = (latInterval[0] + latInterval[1]) / 2.0d;
                if (latitude >= mid) {
                    base32CharIndex = (base32CharIndex << 1) + 1;
                    latInterval[0] = mid;
                } else {
                    base32CharIndex = (base32CharIndex << 1);
                    latInterval[1] = mid;
                }
            }

            isEvenBit = !isEvenBit;
            bit++;

            if (bit == 5) {
                geohash.append(BASE32[base32CharIndex]);
                bit = 0;
                base32CharIndex = 0;
            }
        }

        return geohash.toString();
    }
}


